﻿using System;
using System.Diagnostics;
using System.Threading;

namespace InDepth2Demo.Chapter15
{
    // 线程同步技术是指在多线程程序中，为了保证后者线程，只有等待前者线程完成之后才能继续执行。这就好比生活中排队买票，在前面的人没买到票之前，后面的人必须等待。

    // 多线程应用程序可以提高程序的性能，并提供更好的用户体验。然而当我们创建了多个线程后，它们就有可能同时去访问某一个共享资源，这将损坏资源中所保存的数据。
    // 在这种情况下，我们需要使用线程同步技术，确保某一时刻只有一个线程在操作共享资源。

    internal class MultiThread_Sync
    {
        // 多线程程序中存在的隐患
        // 这是模拟火车票购票系统的代码。
        public static void Call()
        {
            Thread thread1 = new Thread(SaleTicketThread1);     // 主线程 创建了两个出票线程，然后调用 Start 函数使这两个线程开始运行，即进行卖票。
            Thread thread2 = new Thread(SaleTicketThread2);
            thread1.Start();
            thread2.Start();

            Thread.Sleep(4000);
            Console.WriteLine("4秒后票应该已经出完了");
        }

        static int tickets = 100;           // 定义了一个全局静态变量tickets，用以表示剩余票数，这里初始化为100，同时创建了两个线程负责销售这100张票。

        private static void SaleTicketThread1()
        {
            while (true)
            {
                if (tickets > 0)                                    // 在while循环中判断tickets变量的值。如果tickets大于0，就销售一张票，输出“线程1出票：”
                    Console.WriteLine("线程1出票：" + tickets--);
                else
                    break;
            }
        }
        private static void SaleTicketThread2()
        {
            while (true)
            {
                if (tickets > 0)
                    Console.WriteLine("线程2出票：" + tickets--);
                else
                    break;
            }
        }
        // 线程1和线程2在售票时，出售的火车票号码并不连续，说明以上应用程序的售票过程是不正确的，这也就是多线程程序所存在的问题，因为两个线程访问了同一个全局静态变量——tickets。

        // 为了避免这种情况的发生，我们需要对多个线程进行同步处理，保证在同一时间内只有一个线程访问共享资源，以及保证前面的线程售票完成后，后面的线程才会访问资源。

        static int tickets2 = 100;
        static object gloalObj = new object();  // 辅助对象

        public static void SaleTicketThread3()
        {
            while (true)
            {
                try
                {
                    Monitor.Enter(gloalObj);    // 在 gloalObje 对象上获得排他锁
                    Thread.Sleep(1);
                    if (tickets2 > 0)
                        Console.WriteLine("线程3出票：" + tickets2--);
                    else
                        break;
                }
                finally
                {
                    Monitor.Exit(gloalObj);     // 释放指定对象上的排他锁
                }
            }
        }

        public static void SaleTicketThread4()
        {
            while (true)
            {
                try
                {
                    Monitor.Enter(gloalObj);    // 在object对象上获得排他锁
                    Thread.Sleep(1);
                    if (tickets2 > 0)
                        Console.WriteLine("线程4出票：" + tickets2--);
                    else
                        break;
                }
                finally
                {
                    Monitor.Exit(gloalObj);     // 释放指定对象上的排他锁
                }
            }
        }

        // 使用监视器对象实现线程同步
        public static void Call2()
        {
            Thread thread1 = new Thread(SaleTicketThread3);
            Thread thread2 = new Thread(SaleTicketThread4);
            thread1.Start();
            thread2.Start();
            Thread.Sleep(4000);
            Console.WriteLine("4秒后票应该已经出完了");
        }

        // 额外定义了一个静态全局变量 gloalObj，并将其作为参数传递给 Enter 方法。
        // 使用了 Monitor 锁定的对象需要为引用类型，而不能为值类型。因为在将值类型变量传递给Enter时，它将被先装箱为一个单独的对象，之后再传递给Enter方法；而在将变量传递给Exit方法时，也会创建一个单独的引用对象。
        // 此时，传递给Enter方法的对象和传递给Exit方法的对象不同，Monitor将会引发 SynchronizationLockException 异常。



        // 锁的使用对性能造成的影响
        public static void Call3()
        {
            int x = 0;
            const int iterationNumber = 5000000;  // 迭代次数为500万次
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < iterationNumber; i++)
            {
                x++;
            }
            Console.WriteLine("不使用锁的情况下花费的时间 :{0} ms", sw.ElapsedMilliseconds);
            sw.Restart();

            for (int i = 0; i < iterationNumber; i++)  // 使用锁的情况
            {
                Interlocked.Increment(ref x);
            }

            Console.WriteLine("使用锁的情况下花费的时间 :{0} ms", sw.ElapsedMilliseconds);
            Console.Read();
        }
    }
}

